#!/usr/bin/env python

# Copyright (c) 2015-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the license found in the LICENSE file in
# the root directory of this source tree.

from __future__ import print_function

import logging
import optparse
import os
import os.path
import subprocess
import sys
from datetime import datetime

# Set up the logging early on in the process.
logging.basicConfig(level=logging.INFO, format='%(message)s')

# Add the lib/ directory to $PYTHONPATH so library code can be imported.
sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))

import utils
from dependencies import check_dependencies
from js_test_runner import JsTestRunner
from lint_py_headers import LintPyHeaders
from package_manager import PackageManager, NUCLIDE_PATH

# Supress stack traces when stopped by CTRL+C.
def keyboard_interrupt(exctype, value, traceback):
    if exctype == KeyboardInterrupt:
        print()
    else:
        sys.__excepthook__(exctype, value, traceback)
sys.excepthook = keyboard_interrupt

# Parse the command-line arguments.
parser = optparse.OptionParser(
    usage='usage: %prog [options] [package-names or integration-tests]',
    description='Run all tests for Nuclide. '
                'Can explicitly list individual packages or integration-tests '
                'to run.')
parser.add_option('--dev', action='store_true',
                  help='Target ~/.atom/dev/packages/ instead of ~/.atom/packages',
                  default=False)
parser.add_option('--no-module-cycle-check', action='store_true',
                  help='Skip `./module_cycle_check` step')
parser.add_option('--verbose', action='store_true',
                  help='Verbose output')
parser.add_option('--no-atom', action='store_true',
                  help='Exclude packages that depend on Atom')
parser.add_option('--parallel', action='store_true',
                  help='Run parallelable tests in parallel')
parser.add_option('--no-version', action='store_true',
                  help='Ignore mismatched versions of Atom/npm/node/apm')
parser.add_option('--run-integration-tests', action='store_true',
                  help='Only run tests in Nuclide/spec')
parser.add_option('--continue-on-errors', action='store_true',
                  help='Run all tests, regardless of failures')
parser.add_option('--destructive-compile', action='store_true',
                  help='Transpile and generate RPC proxies as if this were a '
                       'prod build. THIS WILL DESTROY THE ORIGINAL SOURCE!!!',
                  default=False)

options, args = parser.parse_args(sys.argv[1:])

try:
    from fb.pre_setup import fb_pre_setup
    disable_fb_update = not options.dev
    fb_pre_setup(disable_fb_update)
except ImportError as _:
    pass

def run(name, cmd):
    try:
        start = datetime.now()
        logging.info('Running %s...', name)
        subprocess.check_call(cmd, cwd=NUCLIDE_PATH)
    except subprocess.CalledProcessError as err:
        logging.info('FAILED: %s', name)
        sys.exit(err.returncode)
    else:
        end = datetime.now()
        logging.info('Finished %s (%s seconds)', name, (end - start).seconds)

def lint_py_headers():
    print('Linting python file license headers.')
    errors = LintPyHeaders().get_errors()
    for lint_error in errors:
        print(lint_error, file=sys.stderr)
    if errors:
        raise utils.TestFailureError('FAILED: Python file license header lint.')

def run_compile_steps(destructive_compile=False):
    if destructive_compile:
        # This will destroy the original source and create untracked files!
        run('generate_proxies',
            ['scripts/dev/release_generate_proxies.js', '--save'])
        run('transpile',
            ['scripts/dev/release_transpile.js', '--overwrite'])
    else:
        # This only warms up the transpile cache.
        run('transpile', ['scripts/dev/release_transpile.js'])

# Some tests compare created files and all their properties to an oracle. If
# they are created with a different umask, then the permissions are different
# and the tests fail.
os.umask(022)

package_manager = PackageManager()
test_runner = JsTestRunner(
    package_manager,
    not options.no_atom,
    args,
    options.verbose,
    options.parallel,
    options.continue_on_errors,
)

if not options.no_version:
    check_dependencies(not options.no_atom)

try:
    if options.run_integration_tests:
        run_compile_steps(destructive_compile=options.destructive_compile)
        test_runner.run_integration_tests()
    else:
        run('lint_packages', ['scripts/dev/lint_packages'])
        run('flow', ['flow', 'check', '--show-all-errors'])
        run('eslint', ['scripts/lint-eslint.js'])
        lint_py_headers()
        if not options.no_module_cycle_check:
            run('module_cycle_check', ['scripts/dev/module_cycle_check'])
        # Some tests have fixtures that are services. These should be declared
        # in a services-3.json file first.
        run_compile_steps(destructive_compile=False)
        test_runner.run_unit_tests()
except utils.TestFailureError as err:
    logging.info(err)
    sys.exit(err.code)
